#include "RenderingSystem.h"
#include <vector>
#include "Photon.h"
#include "PhongShader.h"
#include <omp.h>


using namespace std;

float RenderingSystem::SoftShadow(Vector3 start, int nr_of_tests)
{
    Vector3 hit;
    float hits = 0;
    Ray hit_ray;
    bool intersect;

    for (int i = 0; i < nr_of_tests; i++)
    {
        Vector3 lightPos = (_CurrentScene->GetLightSources()->front())->GetPosition();
        float dist = (start - lightPos).LengthPow2();
        Vector3 random_kernel = Vector3::normalize(lightPos-start) + 0.06*Vector3::GetRandomNormalizedKernel();
        //cout << tangentspace.Transform(random_dir) << " " << Vector3::dot(normal, tangentspace.Transform(random_dir))  << endl;
        hit_ray.SetRay(random_kernel, start, 1);
        hit_ray.Advance();
        intersect = false;
        for (vector<SurfaceObject*>::iterator itr = _CurrentScene->GetRenderableObjects()->begin(); itr != _CurrentScene->GetRenderableObjects()->end(); itr++)
        {
            if (!intersect && (*itr)->Intersects(hit_ray, hit) && (hit - start).LengthPow2() < dist)
            {
                hits++;
                intersect = true;
                break;
            }
        }
    }

    return hits / nr_of_tests;
}


Vector3 RenderingSystem::GetIndirectLighting(float weight, int gather_ph, Vector3 closest_hit)
{
        Vector3 light(0.0, 0.0, 0.0);
        if (NumberOfPhotons_IndirectLighting <= 0)
            return  light;

        vector<Photon*> closest_photons = _IndirectLightingPhotonMap->FindClosestPhotons(closest_hit, gather_ph, 1.0);
        for (vector<Photon*>::iterator itr = closest_photons.begin(); itr != closest_photons.end(); itr++)
        {
            light = light + (*itr)->_color * 1.0 / (1.0 + weight*(closest_hit-(*itr)->_position).LengthPow2());

        }
        light = light / gather_ph;
}

Vector3 RenderingSystem::GetCaustics(float weight, int gather_ph, Vector3 closest_hit)
{
        Vector3 light(0.0, 0.0, 0.0);
        if (NumberOfPhotons_Caustics <= 0)
            return  light;

        vector<Photon*> closest_photons = _CausticsPhotonMap->FindClosestPhotons(closest_hit, gather_ph, 0.0085);
        for (vector<Photon*>::iterator itr = closest_photons.begin(); itr != closest_photons.end(); itr++)
        {
            light = light + (*itr)->_color * 1.0 / (1.0 + weight*(closest_hit-(*itr)->_position).LengthPow2());

        }
        light = light / gather_ph;
}

Vector3 RenderingSystem::Trace(Ray trace_ray)
{

    if (trace_ray.life < 1)
        return  _CurrentScene->GetBackground();

    trace_ray.life--;

    //Avoiding self-intersections
    trace_ray.Advance();

    Vector3 color = _CurrentScene->GetBackground();
    bool intersect = false;
    Vector3 closest_hit;
    Vector3 hit;
    SurfaceObject * obj = NULL;
    float dist = 100000000;
    float new_dist;

    for (vector<SurfaceObject*>::iterator itr = _CurrentScene->GetRenderableObjects()->begin(); itr != _CurrentScene->GetRenderableObjects()->end(); itr++)
    {
        if ((*itr)->Intersects(trace_ray, hit) && (!intersect || (hit - trace_ray.start).length() < dist))
        {
            new_dist = (hit - trace_ray.start).length();
            if (dist - new_dist < 0.00001)
                continue;
            intersect = true;
            closest_hit = hit;
            dist = (hit - trace_ray.start).length();
            obj = (*itr);

        }
    }

    if (intersect)
    {
        SurfaceShader * ShaderProgram = (obj->_sh);
        color = ShaderProgram->Shade(*obj->_material,
                                        trace_ray,
                                        closest_hit,
                                        *obj,
                                        *_CurrentScene->GetLightSources(),
                                        *this);
    }

    return color;
}

void RenderingSystem::Render(Image &img, Camera &RenderCamera)
{


    if (NumberOfPhotons_IndirectLighting > 0)
    {
        _IndirectLightingPhotonMap = new PhotonMap(*_CurrentScene);
        float TotalEnergy = 0.0f;
        for (vector<LightSource*>::iterator itr = _CurrentScene->GetLightSources()->begin(); itr != _CurrentScene->GetLightSources()->end(); itr++)
            TotalEnergy += (*itr)->GetEnergy();
        _IndirectLightingPhotonMap->CreatePhotonMap(NumberOfPhotons_IndirectLighting, TotalEnergy, *_CurrentScene->GetRenderableObjects(), *_CurrentScene->GetLightSources());
    }

    if (NumberOfPhotons_Caustics > 0)
    {
        _CausticsPhotonMap = new PhotonMap(*_CurrentScene);
        float TotalEnergy = 0.0f;
        vector<SurfaceObject*> targets;
        for (vector<LightSource*>::iterator itr = _CurrentScene->GetLightSources()->begin(); itr != _CurrentScene->GetLightSources()->end(); itr++)
        {
            TotalEnergy += (*itr)->GetEnergy();
        }

        for (vector<SurfaceObject*>::iterator itr = _CurrentScene->GetRenderableObjects()->begin(); itr != _CurrentScene->GetRenderableObjects()->end(); itr++)
        {
            if ((*itr)->_material->GetReflectance() > 0.0 || (*itr)->_material->GetTransparancy() > 0.0)
            targets.push_back(*itr);
        }
        _CausticsPhotonMap->CreatePhotonMap(NumberOfPhotons_Caustics, TotalEnergy, targets, *_CurrentScene->GetLightSources());
    }




    Vector3  *c;
    float ratio = ((float)img.y / (float)img.x);
    Ray traceThis;

    int last = -1;


    for (int i = 0; i < img.x; i++)
    {

        #pragma omp parallel for schedule(dynamic)
        for (int j = 0; j < img.y; j++)
        {
            traceThis.SetRay(Vector3((float)i/(float)img.x,(float)ratio*j/(float)img.y, 0) - RenderCamera.GetCameraPosition(), Vector3(0,0,0), 10);
            traceThis._RefractiveIndex = 1.0;
            c = &Trace(traceThis);
            img.data[i][j][0] = c->x;
            img.data[i][j][1] = c->y;
            img.data[i][j][2] = c->z;
            if (j == img.y -1 || j == 0 || i == img.x -1 || i == 0)
            {
                img.data[i][j][0] = 1;
                img.data[i][j][1] = 0;
                img.data[i][j][2] = 0;
            }
        }

        if (last != (int)(((100)*i)/(float)img.x))
        {
            last = (int)((100)*i)/(float)img.x;
            cout << last+1 << "% " << flush;
        }

    }
    cout << endl;
}
